#include <QPainter>
#include <QApplication>
#include <QPrinter>
#include <QPixmap>
#include <QMessageBox>
#include <cmath>

#include "field.h"

Field::Field(QWidget *parent)
    : QWidget(parent)
{
    currentZoom = 100;
    zoom_x=0;
    zoom_y=0;
    zoomChange=0;
    gridOn=true;
    showVertexNames=false;
    showTriangleNames=false;
    showContours=false;
    curViewMode=EDefault;
    dragVector=QPoint();
    renderedImage=QImage();
    pThread = new RenderPseudoThread();

    //connect(&thread, SIGNAL(workDone(const QImage &)), this, SLOT(updateImage(const QImage &)));

    connect(pThread, SIGNAL(workDone(const QImage &)), this, SLOT(updateImage(const QImage &)));


    setPalette(QPalette(QColor(200, 200, 200)));
    setAutoFillBackground(true);

    parser = new fileParser();
}

Field::~Field()
{    
    delete pThread;
    delete parser;
}

void Field::loadColorBar(colorBar* aColBar)
{
    colBar=aColBar;
}

void Field::loadFile(QString fileName){
    try {
        parser->parse(fileName);
    } catch (fileParser::BadFileStructureEx /* ex */){
        QMessageBox::warning(0, "Error", "Incorrect file structure");
        return;
    } catch (fileParser::FileNotOpenEx /* ex */) {
        QMessageBox::warning(0, "Error", "Can't open file");
    }

    parser->populate(pThread);
    emit colorBarChanged(parser->min, parser->max);
    if (parser->currentMode!=fileParser::EScalar1){
        emit enableContours(false);
    } else {
        emit enableContours(true);
    }
    renderedImage=QImage();
    update();
}

void Field::exportImage(QString fileName){
    QImage colBarImage=colBar->image();
    int space=10;
    QImage *image =  new QImage(this->width()+space+colBarImage.width(), this->height(),  QImage::Format_ARGB32);
    image->fill(qRgb(255,255,255));
    QPainter* painter = new QPainter(image);
    doPaint(*painter, true);
    painter->drawImage(this->width()+space, 0, colBarImage);
    //p->end();
    delete painter;
    if (!image->save(fileName, 0, 100)){
        //TODO: check to .ps extension
        QPrinter printer;
        printer.setFullPage(true);
        printer.setPageSize(QPrinter::Custom);
        printer.setPaperSize(QSize(image->widthMM(), image->heightMM()), QPrinter::Millimeter);
        printer.setOutputFormat(QPrinter::PostScriptFormat);
        printer.setOutputFileName(fileName);
        QPainter printPainter(&printer);
        printPainter.drawImage(0, 0, *image);
    }
    delete image;
}

void Field::updateImage(const QImage &image){
    if (parser->currentMode==fileParser::EScalar1){
        renderedImage=image;
        update();
    }
}

void Field::defaultViewToggled(){   //TODO: rename to defaultViewTriggered
    curViewMode=EDefault;
    this->setCursor(QCursor(Qt::ArrowCursor));
}

void Field::zoomInToggled(){
    curViewMode=EZoomIn;
    this->setCursor(QCursor(QPixmap(":/images/zoom-in.svg")));
}

void Field::zoomOutToggled(){
    curViewMode=EZoomOut;
    this->setCursor(QCursor(QPixmap(":/images/zoom-out.svg")));
}

void Field::wheelEvent(QWheelEvent *event)
{
    int zoomChange=20;  //TODO: better name, zoomDelta, zoom...
    if (event->delta() > 0)
    {
        setZoom(currentZoom + zoomChange);
        zoomChange=5;
    }
    else
    {
        setZoom(currentZoom - zoomChange);
        zoomChange=-5;
    }
    zoom_x=event->x();
    zoom_y=event->y();
    renderedImage=QImage();
    update();
    event->ignore();
}

void Field::mousePressEvent(QMouseEvent *event){
    if (event->button() == Qt::LeftButton) {
        if (curViewMode==EDefault) {
            dragStartPosition=event->pos();
        } else {
            int zoomChange=20;  //TODO: refactor
            if (curViewMode==EZoomIn) {
                setZoom(currentZoom + zoomChange);
                zoomChange=5;
            }
            if  (curViewMode==EZoomOut) {
                setZoom(currentZoom - zoomChange);
                zoomChange=-5;
            }
            zoom_x=event->x();
            zoom_y=event->y();
            renderedImage=QImage();
            update();
        }
    }
}

void Field::mouseMoveEvent(QMouseEvent *event){
    if (curViewMode!=EDefault)
        return;
    if (!(event->buttons() & Qt::LeftButton))  //TODO: refactor this
         return;
     if ((event->pos() - dragStartPosition).manhattanLength()
          < QApplication::startDragDistance())
         return;

     dragVector+=event->pos() - dragStartPosition;
     renderedImage=QImage();
     update();
     dragStartPosition=event->pos();
}

void Field::resizeEvent(QResizeEvent */*event*/){
    if (width()>renderedImage.width() || height()>renderedImage.height()){
        renderedImage=QImage();
    }
}

void Field::setZoom(int zoom)
{
    if (zoom < 5){
        zoom = 5;
    }
    if (currentZoom == zoom){
        return;
    }
    currentZoom = zoom;
    update();
    emit zoomChanged(currentZoom);
}

void Field::grid(bool on){
    gridOn=on;
    update();
}

void Field::vertexNames(bool on){
    showVertexNames=on;
    update();
}

void Field::triangleNames(bool on){
    showTriangleNames=on;
    update();
}

void Field::contours(bool on){
    showContours=on;
    update();
}

void Field::paintEvent(QPaintEvent * /* event */)
{
    QPainter painter(this);
    doPaint(painter);
}

void Field::doPaint(QPainter& painter, bool exporting){
    int height=270;
    painter.translate(0,height);

    painter.translate(dragVector);
    //TODO: complete refactor zoom
    painter.scale(1,-1);
    QPoint zoomPoint1=painter.matrix().map(QPoint(zoom_x, zoom_y));
    //painter.drawLine(zoomPoint1, zoomPoint1+QPoint(30,0));
    painter.scale(currentZoom, currentZoom);
    QPointF zoomPoint2=(float)1/currentZoom*(QPointF(zoomPoint1));
    QPointF zoomPoint3=(float)1/(currentZoom-zoomChange)*(QPointF(zoomPoint1));
    //painter.drawLine(zoomPoint2, zoomPoint3);
    painter.translate(zoomPoint2-zoomPoint3);
    QImage image;
    if (renderedImage.isNull() || showContours){
        image =  QImage(this->size(), QImage::Format_ARGB32);
        if (exporting){
            image.fill(qRgb(255,255,255));
        } else {
            image.fill(qRgb(200,200,200));
        }
    } else {
        image = renderedImage;
    }
    QMatrix currnetMatrix = painter.matrix();
    parser->draw(&image, currnetMatrix, pThread, gridOn, showVertexNames, showTriangleNames, showContours);
    //TODO: world transformation matrix is messed here from draw(...) method
    painter.setWorldMatrixEnabled(false);
    if (exporting){ //fill background with white color
        painter.fillRect(0,0,this->width(), this->height(), qRgb(255,255,255));
    }
    painter.drawImage(0,0,image);



    //Compute stuff common for both vertical and horizontal scales
    QFontMetrics fm(this->font());
    QRect numbersRect = fm.boundingRect("0123456789");
    int h = this->height()-numbersRect.height();

    //Draw vertical scale
    QPointF bottomLeftV = currnetMatrix.inverted().map(QPointF(0,h));
    QPointF topLeft = currnetMatrix.inverted().map(QPointF(0,0));
    float yMin = bottomLeftV.y();
    float yMax = topLeft.y();
    float yLength=yMax-yMin;
    float coef=1.6; // if this 1.4 then when xLength is less then 1.4 (14, ...), mark step is 0.1 (1, ...)
    int n=0;
    while (yLength>pow(10,n+1)){
        n++;
    }
    while (yLength<pow(10,n)*coef){
        n--;
    }
    int stepDivided=1;  //no halves
    float halvesCoef=3.5;
    if (yLength>pow(10,n)*coef && yLength<pow(10,n)*halvesCoef){
        stepDivided=2;
    }
    float markMin=ceil(yMin*pow(10,-n)*stepDivided)*pow(10,n)/stepDivided;
    float markMax=floor(yMax*pow(10,-n)*stepDivided)*pow(10,n)/stepDivided;
    float markStep=pow(10,n)/stepDivided;

    int maxTextLenght=0;
    float zero=1e-7;
    for (float mark=markMin; mark<=markMax; mark+=markStep){
        if (fabs(mark)<zero){   //workaround: there is sometimes somothing like 1.1407e-7 instead of 0
            mark=0;
        }
        QRect r = fm.boundingRect(QString::number(mark));
        if (r.width()>maxTextLenght){
            maxTextLenght=r.width();
        }
    }
    QPoint p3(0,0);
    QPoint p4(maxTextLenght, this->height());
    QRect vRect(p3,p4);
    QPalette pal;
    QColor fillColor=pal.color(QPalette::Window);
    if (exporting){
        fillColor=QColor(Qt::white);
    }
    painter.fillRect(vRect, fillColor);
    painter.drawLine(maxTextLenght,0,maxTextLenght,this->height());

    int markLenght=5;
    for (float mark=markMin; mark<=markMax; mark+=markStep){
        QPointF markPoint(0, mark);
        markPoint=currnetMatrix.map(markPoint);
        markPoint.setX(0);
        if (fabs(mark)<zero){   //workaround: there is sometimes somothing like 1.1407e-7 instead of 0
            mark=0;
        }
        painter.drawText(markPoint, QString::number(mark));
        painter.drawLine(maxTextLenght, markPoint.y(),maxTextLenght+markLenght,markPoint.y());
    }

    //Draw horisontal scale
    QPointF bottomLeft = currnetMatrix.inverted().map(QPointF(maxTextLenght, this->height()));
    QPointF topRight = currnetMatrix.inverted().map(QPointF(this->width(), 0));
    float xMin = bottomLeft.x();
    float xMax = topRight.x();
    float xLength=xMax-xMin;    
    n=0;
    while (xLength>pow(10,n+1)){
        n++;
    }
    while (xLength<pow(10,n)*coef){
        n--;
    }
    stepDivided=1;  //no halves
    //halvesCoef=3.5;   //defined earlier
    if (xLength>pow(10,n)*coef && xLength<pow(10,n)*halvesCoef){
        stepDivided=2;
    }
    markMin=ceil(xMin*pow(10,-n)*stepDivided)*pow(10,n)/stepDivided;
    markMax=floor(xMax*pow(10,-n)*stepDivided)*pow(10,n)/stepDivided;
    markStep=pow(10,n)/stepDivided;

    QPoint p1(maxTextLenght,this->height());
    QPoint p2(this->width(), h);
    QRect hRect(p1,p2);
    painter.fillRect(hRect, fillColor);
    painter.drawLine(maxTextLenght,h,this->width(),h);

    for (float mark=markMin; mark<=markMax; mark+=markStep){
        QPointF markPoint(mark, 0);
        markPoint=currnetMatrix.map(markPoint);
        markPoint.setY(this->height());
        if (fabs(mark)<zero){   //workaround: there is sometimes somothing like 1.1407e-7 instead of 0
            mark=0;
        }
        painter.drawText(markPoint, QString::number(mark));
        painter.drawLine(markPoint.x(), h,markPoint.x(),h-markLenght);
    }
}
